import asyncio
import websockets
import json
import datetime
import uuid
import logging
import os

# --- 配置项 ---
HOST = '0.0.0.0'  # 监听所有网络接口
PORT = 8080
AC_STATUS_FILE = 'ac_status.json'
TIMERS_FILE = 'timers.json'
LOG_LEVEL = logging.INFO

# --- 日志设置 ---
logging.basicConfig(level=LOG_LEVEL, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

# --- 全局状态 ---
CLIENTS = set()
DEVICES = {}  # 存储 websocket: device_type 映射关系

AC_STATUS = {
    "switch": False,
    "mode": "kGreeCool",  # 默认模式，来自客户端
    "temp": 25,
    "fan": 1,
    "vf": False,  # 垂直摆风
    "light": True,  # 面板灯光
    "sleep": False,
    "turbo": False
}
TIMER_TASKS = []  # 定时任务列表


# 定时任务格式示例:
# { "id": "uuid字符串", "type": "on"/"off", "time": "HH:MM", "days": [0-6], "enabled": True }
# 客户端日期: 0=星期日, 1=星期一, ..., 6=星期六

# --- 持久化功能 ---
def load_ac_status():
    global AC_STATUS
    if os.path.exists(AC_STATUS_FILE):
        try:
            with open(AC_STATUS_FILE, 'r') as f:
                AC_STATUS = json.load(f)
                logger.info(f"从 {AC_STATUS_FILE} 加载了空调状态")
        except Exception as e:
            logger.error(f"加载空调状态出错: {e}。使用默认值。")
    else:
        logger.info(f"未找到 {AC_STATUS_FILE}。使用默认空调状态。")
        save_ac_status()  # 如果文件不存在则保存默认值


def save_ac_status():
    try:
        with open(AC_STATUS_FILE, 'w') as f:
            json.dump(AC_STATUS, f, indent=4)
            logger.debug(f"已将空调状态保存到 {AC_STATUS_FILE}")
    except Exception as e:
        logger.error(f"保存空调状态时出错: {e}")


def load_timers():
    global TIMER_TASKS
    if os.path.exists(TIMERS_FILE):
        try:
            with open(TIMERS_FILE, 'r') as f:
                TIMER_TASKS = json.load(f)
                logger.info(f"从 {TIMERS_FILE} 加载了 {len(TIMER_TASKS)} 个定时任务")
        except Exception as e:
            logger.error(f"加载定时任务出错: {e}。启用空列表。")
            TIMER_TASKS = []
    else:
        logger.info(f"未找到 {TIMERS_FILE}。不加载定时任务。")
        TIMER_TASKS = []


def save_timers():
    try:
        with open(TIMERS_FILE, 'w') as f:
            json.dump(TIMER_TASKS, f, indent=4)
            logger.debug(f"已将定时任务保存到 {TIMERS_FILE}")
    except Exception as e:
        logger.error(f"保存定时任务时出错: {e}")


# --- 广播功能 ---
async def broadcast(message_payload):
    if CLIENTS:
        message_json = json.dumps(message_payload)
        # 创建任务列表以并发发送消息
        tasks = [asyncio.create_task(client.send(message_json)) for client in CLIENTS]
        results = await asyncio.gather(*tasks, return_exceptions=True)
        for i, result in enumerate(results):
            if isinstance(result, Exception):
                # 处理异常，例如客户端突然断开
                client_list = list(CLIENTS)  # 获取索引的快照
                logger.warning(f"向客户端 {client_list[i].remote_address} 广播时出错: {result}")
                # 可选择在这里移除有问题的客户端，虽然主处理程序也应该这样做
    logger.debug(f"已广播: {message_payload}")


async def broadcast_ac_status():
    await broadcast(AC_STATUS)


# 专门向空调控制设备发送消息
async def send_to_ac_devices(message_payload):
    """仅向空调控制设备发送消息"""
    for client in CLIENTS:
        if DEVICES.get(client) == 'ac_controller':
            try:
                await client.send(json.dumps(message_payload))
                logger.debug(f"已向空调控制设备 {client.remote_address} 发送: {message_payload}")
            except Exception as e:
                logger.error(f"向空调设备 {client.remote_address} 发送消息失败: {e}")


# --- 客户端注册 ---
async def register_client(websocket):
    CLIENTS.add(websocket)
    logger.info(f"客户端已连接: {websocket.remote_address}。总客户端数: {len(CLIENTS)}")


async def unregister_client(websocket):
    CLIENTS.remove(websocket)
    if websocket in DEVICES:
        device_type = DEVICES.pop(websocket)
        logger.info(f"{device_type} 设备已断开: {websocket.remote_address}。总客户端数: {len(CLIENTS)}")
    else:
        logger.info(f"客户端已断开: {websocket.remote_address}。总客户端数: {len(CLIENTS)}")


# --- 消息处理逻辑 ---
async def handle_message(websocket, message_str):
    """
    处理来自客户端的WebSocket消息

    Args:
        websocket: WebSocket连接对象
        message_str: 收到的消息字符串
    """
    try:
        # 处理特殊字符串命令，不尝试JSON解析
        if message_str == 'get_status':
            logger.info(f"向 {websocket.remote_address} 发送空调状态")
            await websocket.send(json.dumps(AC_STATUS))
            return

        # 设备注册消息
        if message_str == 'register:ac_device':
            DEVICES[websocket] = 'ac_controller'
            logger.info(f"注册了空调控制设备: {websocket.remote_address}")
            # 立即发送当前状态
            await websocket.send(json.dumps(AC_STATUS))
            return

        if message_str == 'register:web_client':
            DEVICES[websocket] = 'web_client'
            logger.info(f"注册了Web客户端: {websocket.remote_address}")
            # Web客户端注册后也发送当前状态
            await websocket.send(json.dumps(AC_STATUS))
            return

        # 尝试解析JSON
        data = json.loads(message_str)
        logger.debug(f"收到来自 {websocket.remote_address} 的消息: {data}")

        # 处理JSON格式的消息
        if isinstance(data, dict):
            command = data.get('command')
            timer_command = data.get('timer_command')

            if command == 'set_status':
                parameter = data.get('parameter')
                value = data.get('value')
                if parameter in AC_STATUS:
                    # 必要时进行基本类型转换，虽然客户端应该发送正确类型
                    if isinstance(AC_STATUS[parameter], bool):
                        value = bool(value)
                    elif isinstance(AC_STATUS[parameter], int) and parameter != "fan":
                        try:
                            value = int(value)
                        except ValueError:
                            logger.warning(f"{parameter} 的值无效: {value}")
                            return
                    elif parameter == "fan":
                        try:
                            value = int(value)
                        except ValueError:
                            logger.warning(f"风扇值无效: {value}")
                            return

                    AC_STATUS[parameter] = value
                    logger.info(f"空调状态已更新: {parameter} = {value}。新状态: {AC_STATUS}")
                    save_ac_status()
                    # 广播给所有客户端
                    await broadcast_ac_status()
                    # 专门发送给空调控制设备
                    await send_to_ac_devices({"command": "update_status", "status": AC_STATUS})
                else:
                    logger.warning(f"未知的空调参数: {parameter}")

            elif timer_command:
                await handle_timer_command(websocket, timer_command, data)

            else:
                logger.warning(f"未知的指令结构: {data}")
        else:
            logger.warning(f"收到非字典/非'get_status'消息: {message_str}")

    except json.JSONDecodeError:
        logger.error(f"无法解析JSON消息: {message_str}")
    except Exception as e:
        logger.error(f"处理消息时出错: {e}，消息内容: {message_str}", exc_info=True)


async def handle_timer_command(websocket, timer_command, data):
    global TIMER_TASKS
    response = {}

    if timer_command == 'get_timers':
        response = {"timer_response": "timers_list", "tasks": TIMER_TASKS}
        logger.info(f"向 {websocket.remote_address} 发送定时任务列表")
        await websocket.send(json.dumps(response))

    elif timer_command == 'add_timer':
        try:
            new_task = {
                "id": str(uuid.uuid4()),
                "type": data.get("type"),  # 'on' 或 'off'
                "time": data.get("time"),  # "HH:MM"
                "days": data.get("days", []),  # [0-6] (周日-周六)
                "enabled": data.get("enabled", True)
            }
            # 基本验证
            if not (new_task["type"] in ["on", "off"] and \
                    isinstance(new_task["time"], str) and len(new_task["time"]) == 5 and \
                    isinstance(new_task["days"], list) and \
                    isinstance(new_task["enabled"], bool)):
                raise ValueError("无效的定时任务数据")

            TIMER_TASKS.append(new_task)
            save_timers()
            response = {"timer_response": "timer_added", "task": new_task}
            logger.info(f"定时任务已添加: {new_task['id']} 由 {websocket.remote_address}")
            await websocket.send(json.dumps(response))
            # 通知空调控制设备定时任务已更新
            await send_to_ac_devices({"command": "timers_updated", "tasks": TIMER_TASKS})
        except Exception as e:
            logger.error(f"添加定时任务时出错: {e}, 数据: {data}")
            await websocket.send(json.dumps({"timer_response": "error", "message": str(e)}))

    elif timer_command == 'update_timer':
        task_id = data.get('id')
        updated_task = None
        for task in TIMER_TASKS:
            if task['id'] == task_id:
                # 客户端仅发送'enabled'用于切换状态，但可以扩展
                if 'enabled' in data:
                    task['enabled'] = bool(data['enabled'])
                # 如有必要，在此处添加其他可更新字段
                # task['time'] = data.get('time', task['time'])
                # task['days'] = data.get('days', task['days'])
                # task['type'] = data.get('type', task['type'])
                updated_task = task
                break
        if updated_task:
            save_timers()
            response = {"timer_response": "timer_updated", "task": updated_task}
            logger.info(f"定时任务已更新: {task_id} 由 {websocket.remote_address}")
            # 通知空调控制设备定时任务已更新
            await send_to_ac_devices({"command": "timers_updated", "tasks": TIMER_TASKS})
        else:
            response = {"timer_response": "error", "message": "未找到定时任务"}
            logger.warning(f"更新失败，未找到定时任务: {task_id}")
        await websocket.send(json.dumps(response))

    elif timer_command == 'delete_timer':
        task_id = data.get('id')
        original_len = len(TIMER_TASKS)
        TIMER_TASKS = [task for task in TIMER_TASKS if task['id'] != task_id]
        if len(TIMER_TASKS) < original_len:
            save_timers()
            response = {"timer_response": "timer_deleted", "id": task_id}
            logger.info(f"定时任务已删除: {task_id} 由 {websocket.remote_address}")
            # 通知空调控制设备定时任务已更新
            await send_to_ac_devices({"command": "timers_updated", "tasks": TIMER_TASKS})
        else:
            response = {"timer_response": "error", "message": "未找到要删除的定时任务"}
            logger.warning(f"删除失败，未找到定时任务: {task_id}")
        await websocket.send(json.dumps(response))

    else:
        logger.warning(f"未知的定时命令: {timer_command}")
        await websocket.send(json.dumps({"timer_response": "error", "message": "未知的定时命令"}))


# --- WebSocket主处理函数 ---
async def client_handler(websocket):
    await register_client(websocket)
    try:
        # 连接时发送初始状态
        await websocket.send(json.dumps(AC_STATUS))
        # 在客户端请求时发送定时任务列表
        # await websocket.send(json.dumps({"timer_response": "timers_list", "tasks": TIMER_TASKS}))

        async for message_str in websocket:
            await handle_message(websocket, message_str)
    except websockets.exceptions.ConnectionClosedOK:
        logger.info(f"连接正常关闭: {websocket.remote_address}")
    except websockets.exceptions.ConnectionClosedError as e:
        logger.warning(f"连接因错误关闭: {websocket.remote_address}: {e}")
    except Exception as e:
        logger.error(f"客户端处理中的意外错误 {websocket.remote_address}: {e}")
    finally:
        await unregister_client(websocket)


# --- 定时任务调度器 ---
# 存储已触发定时任务的最后时间，以避免重复触发
scheduler_last_triggered_minute = {}  # task_id: "YYYY-MM-DD HH:MM"


async def timer_scheduler():
    global scheduler_last_triggered_minute
    logger.info("定时任务调度器已启动。")
    while True:
        now = datetime.datetime.now()
        current_time_str = now.strftime("%H:%M")
        # Python中周一是0，周日是6
        # 客户端中周日是0，周一是1，...，周六是6
        # 匹配客户端的周日=0系统: current_day_idx = (now.weekday() + 1) % 7
        current_day_idx = (now.weekday() + 1) % 7

        logger.debug(
            f"调度检查: {now.strftime('%Y-%m-%d %H:%M:%S')}, 日期索引(周日=0): {current_day_idx}, 时间: {current_time_str}")

        executed_timer_this_cycle = False
        for task in TIMER_TASKS:
            if task['enabled'] and task['time'] == current_time_str and current_day_idx in task['days']:
                # 检查这个特定任务在这个特定分钟是否已经触发
                trigger_key = f"{task['id']}_{now.strftime('%Y-%m-%d')}_{task['time']}"

                if scheduler_last_triggered_minute.get(task['id']) == trigger_key:
                    logger.debug(f"定时任务 {task['id']} 已在 {trigger_key} 触发过，跳过。")
                    continue

                logger.info(
                    f"执行定时任务: {task['id']} - 类型: {task['type']}, 时间: {task['time']}, 日期: {task['days']}")

                new_ac_switch_state = (task['type'] == 'on')

                if AC_STATUS['switch'] != new_ac_switch_state:
                    AC_STATUS['switch'] = new_ac_switch_state
                    executed_timer_this_cycle = True
                    logger.info(f"定时任务 {task['id']} 将空调电源切换为 {AC_STATUS['switch']}")
                else:
                    logger.info(f"定时任务 {task['id']} 试图将空调电源设为 {new_ac_switch_state}，但已经处于该状态。")

                scheduler_last_triggered_minute[task['id']] = trigger_key

        if executed_timer_this_cycle:
            save_ac_status()
            await broadcast_ac_status()
            # 专门向空调控制设备发送更新
            await send_to_ac_devices({"command": "update_status", "status": AC_STATUS})
            logger.info("由于定时任务执行，空调状态已更新并广播。")

        # 清理scheduler_last_triggered_minute中的旧条目以防止内存增长
        # 仅保留当天或可能跨越午夜的任务的条目（这里不太重要）
        current_date_str = now.strftime('%Y-%m-%d')
        keys_to_delete = [
            k for k, v_date_time_str in scheduler_last_triggered_minute.items()
            if not v_date_time_str.startswith(f"{k.split('_')[0]}_{current_date_str}")
        ]
        for key_prefix in keys_to_delete:
            # 此逻辑用于清理有点缺陷。更好的方式: 仅存储 task_id: datetime_object_of_last_trigger
            # 现在，让我们简化为仅限制字典大小或清理非常旧的条目
            pass  # 需要更强大的清理或接受适度数量任务的情况

        await asyncio.sleep(20)  # 每20秒检查一次


# --- 主执行 ---
async def main():
    load_ac_status()
    load_timers()

    # 启动定时调度器任务
    scheduler_task = asyncio.create_task(timer_scheduler())

    async with websockets.serve(client_handler, HOST, PORT):
        logger.info(f"WebSocket服务器已在 ws://{HOST}:{PORT} 启动")
        await asyncio.Future()  # 永远运行


if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        logger.info("服务器关闭中...")
    except Exception as e:
        logger.critical(f"服务器启动或运行失败: {e}")